package com.lzw.breezeNovel.webApp.service.impl.common;

import com.lzw.breezeNovel.common.constant.RedisConstant;
import com.lzw.breezeNovel.common.exception.BaseException;
import com.lzw.breezeNovel.common.login.LoginUserHolder;
import com.lzw.breezeNovel.common.result.ResultCodeEnum;
import com.lzw.breezeNovel.common.utils.CodeUtils;
import com.lzw.breezeNovel.common.utils.JwtUtil;
import com.lzw.breezeNovel.common.utils.PasswordUtil;
import com.lzw.breezeNovel.model.entity.pojo.user.LoginInfo;
import com.lzw.breezeNovel.model.entity.pojo.user.UserInfo;
import com.lzw.breezeNovel.model.entity.vo.CaptchaVo;
import com.lzw.breezeNovel.model.enums.user.BaseGender;
import com.lzw.breezeNovel.model.enums.user.BaseStatus;
import com.lzw.breezeNovel.webApp.event.UserFindPassowrdEvent;
import com.lzw.breezeNovel.webApp.event.UserLoginEvent;
import com.lzw.breezeNovel.webApp.event.UserRegisterEven;
import com.lzw.breezeNovel.webApp.mapper.user.UserInfoMapper;
import com.lzw.breezeNovel.webApp.service.common.SmsService;
import com.lzw.breezeNovel.webApp.service.user.UserInfoService;
import com.lzw.breezeNovel.webApp.vo.user.UserInfoVo;
import com.wf.captcha.SpecCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Clock;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;


/**
 * @author 清风明月
 * @Package com.lzw.breezeNovel.webApp.service.novel.impl
 * @date 2025/2/27
 * @description 登入服务的实现类
 */
@Slf4j
@Service
@SuppressWarnings("all")
public class UserInfoServiceImpl implements UserInfoService {

        /**
         * 注入redisTemplate
         */
        @Autowired
        private RedisTemplate<String, String> redisTemplate;

        /**
         * 注入注入userInfoMapper
         */
        @Autowired
        private UserInfoMapper userInfoMapper;
        /**
         * 注入记录日志服务
         */
        @Autowired
        private ApplicationEventPublisher eventPublisher;


        /**
         * 注入短信服务
         */
        @Autowired
        private SmsService smsService;


        /**
         * 获取短信验证码
         * @param phone
         */
        @Override
        public void getCode(String phone) {
                //获取唯一的锁
                String lockKey = String.format("lock:%s", phone);
                String lockValue = UUID.randomUUID().toString();
                //尝试获取分布式锁
                Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, RedisConstant.APP_LOGIN_CODE_RESEND_TIME_SEC, TimeUnit.SECONDS);
                //判断是否获取到了锁
                if (!Boolean.TRUE.equals(locked)) {
                        log.warn("未能获取到锁，手机号: {}", phone);
                        return;
                }
                //获取验证码的key
                try{
                        String codeKey = RedisConstant.APP_LOGIN_PREFIX + phone;
                        // 检查是否已有验证码记录
                        if(Boolean.TRUE.equals(redisTemplate.hasKey(codeKey))) {
                                //判断验证码是否过期
                                long ttl = RedisConstant.APP_LOGIN_CODE_RESEND_TIME_SEC - redisTemplate.getExpire(codeKey);
                                if(ttl < 60) {
                                        throw new BaseException(ResultCodeEnum.APP_SEND_SMS_TOO_OFTEN);
                                }
                                // 生成并发送验证码（无论是否存在都重新生成）
                                String code = CodeUtils.getCode(6);
                                smsService.sendCode(phone, code);
                                // 存储/更新验证码（重置有效期）
                                redisTemplate.opsForValue().set(
                                        codeKey,
                                        code,
                                        RedisConstant.APP_LOGIN_CODE_RESEND_TIME_SEC,
                                        TimeUnit.SECONDS
                                );
                        }
                }finally {
                        // 确保最终释放锁
                        releaseLock(lockKey, lockValue);
                }
        }

        /**
         * 安全释放分布式锁
         */
        private void releaseLock(String lockKey, String lockValue) {
                try {
                        String script =
                                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                                        "   return redis.call('del', KEYS[1]) " +
                                        "else " +
                                        "   return 0 " +
                                        "end";
                        // 设置lua脚本
                        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
                        // 执行lua脚本
                        Long result = redisTemplate.execute(
                                redisScript,
                                Collections.singletonList(lockKey),
                                lockValue
                        );
                        // 判断是否释放成功
                        if (result == null || result == 0) {
                                log.warn("释放锁失败，锁可能已过期或不属于当前实例: {}", lockKey);
                        }
                } catch (Exception e) {
                        log.error("释放锁时发生异常: {}", e.getMessage(), e);
                }
        }

        /**
         * 登录
         * @param userInfo
         * @return
         */
        @Override
        public String login(@NotNull LoginInfo userInfo) {
                //获取手机号
                final String phone = userInfo.getPhone();
                final String codeKey = userInfo.getKey();
                // 原子化验证码校验（使用Lua脚本保证原子操作）
                String actualCode = validateAndDeleteCode(codeKey, userInfo.getCode());
                // 用户信息校验
                UserInfo user = validateUser(phone, userInfo.getPassword());
                // 生成并存储Token
                String token = generateAndStoreToken(user);
                // 记录登录成功日志
                recordLoginSuccess(user);
                return token;
        }

        /**
         * 校验并删除验证码
         * @param codeKey
         * @param inputCode
         * @return
         */
        @NotNull
        private String validateAndDeleteCode(String codeKey, String inputCode) {
                String script =
                        "local code = redis.call('get', KEYS[1])\n" +
                                "if not code then\n" +
                                "    return 'EXPIRED'\n" +
                                "end\n" +
                                "if code ~= string.lower(ARGV[1]) then\n" +
                                "    return 'MISMATCH'\n" +
                                "end\n" +
                                "redis.call('del', KEYS[1])\n" +
                                "return 'SUCCESS'";
                // 原子化验证码校验（使用Lua脚本保证原子操作）
                RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
                // 执行lua脚本
                String result = redisTemplate.execute(
                        redisScript,
                        Collections.singletonList(codeKey),
                        inputCode
                );
                switch (result) {
                        case "EXPIRED":
                                throw new BaseException(ResultCodeEnum.APP_LOGIN_CODE_EXPIRED);
                        case "MISMATCH":
                                throw new BaseException(ResultCodeEnum.APP_LOGIN_CODE_ERROR);
                        case "SUCCESS":
                                return "VALID";
                        default:
                                throw new BaseException(ResultCodeEnum.SERVICE_ERROR);
                }
        }

        /**
         * 用户信息校验
         */
        @NotNull
        private UserInfo validateUser(String phone, String inputPassword) {
                UserInfo user = userInfoMapper.selectByPhone(phone);
                if (user == null) {
                        throw new BaseException(ResultCodeEnum.APP_LOGIN_IS_NOT_REGISTERED);
                }
                // 检查账户状态->判断是否被禁用
                if (user.getStatus() != BaseStatus.ENABLE) {
                        throw new BaseException(ResultCodeEnum.ADMIN_ACCOUNT_DISABLED_ERROR);
                }
                //判断用户是否被锁定
                if (user.getLockAccount() == 1) {
                        throw new BaseException(ResultCodeEnum.ACCOUNT_LOCKED);
                }
                if (!PasswordUtil.matches(inputPassword, user.getPassword())) {
                        // 记录密码错误次数（需实现错误计数逻辑）
                        handlePasswordError(user);
                        throw new BaseException(ResultCodeEnum.APP_LOGIN_PASSWORD_NOT_TRUE);
                }
                return user;
        }

        /**
         * 生成并存储Token
         */
        private String generateAndStoreToken(@NotNull UserInfo user) {
                //生成token的key
                String tokenKey = RedisConstant.APP_LOGIN_PREFIX + user.getPhone();
                // 生成增强型Token
                String token = JwtUtil.createToken(
                        user.getPhone().toString(),
                        user.getPhone()
                );
                redisTemplate.opsForValue().set(
                        tokenKey,
                        token,
                        RedisConstant.APP_LOGIN_TOKEN_TTL_SEC,
                        TimeUnit.HOURS
                );
                return token;
        }

        /**
         * 记录登录成功日志
         */
        private void recordLoginSuccess(@NotNull UserInfo user) {
                // 异步记录日志
                eventPublisher.publishEvent(new UserLoginEvent(this,user.getPhone()));
        }

        /**
         * 密码错误处理
         */
        private void handlePasswordError(@NotNull UserInfo user) {
                // 错误计数键
                String errorKey = RedisConstant.PASSWORD_ERROR_COUNT_PREFIX + user.getPhone();
                try {
                        // 原子递增并获取最新值
                        Long errorCount = redisTemplate.opsForValue().increment(errorKey);
                        // 首次错误设置过期时间 ->在一个小时内如果密码错误的超过了次数就锁定账号
                        if (errorCount != null && errorCount == 1) {
                                redisTemplate.expire(errorKey, 1, TimeUnit.HOURS);
                        }
                        // 达到阈值锁定账户
                        if (errorCount != null && errorCount >= 5) {
                                // 数据库锁定
                                userInfoMapper.lockAccount(user.getUserId());
                                // Redis记录锁定状态
                                String lockKey = RedisConstant.ACCOUNT_LOCK_PREFIX + user.getPhone();
                                redisTemplate.opsForValue().set(
                                        lockKey,
                                        "LOCKED",
                                        24,  // 锁定24小时
                                        TimeUnit.HOURS
                                );
                                // 清除错误计数器
                                redisTemplate.delete(errorKey);
                                throw new BaseException(ResultCodeEnum.ACCOUNT_LOCKED);
                        }
                } catch (RedisConnectionFailureException e) {
                        log.warn("Redis服务异常，密码错误计数功能降级", e);
                }
        }

        /**
         * 注册
         * @param userInfo
         * @return
         */
        @Override
        @Transactional
        public String register(@NotNull LoginInfo registerInfo) {
                // 参数校验已通过@Valid完成
                final String phone = registerInfo.getPhone();
                // 原子化验证码校验（校验+删除）
                validateAndDeleteCode(phone, registerInfo.getCode());
                // 检查用户是否已存在
                checkUserExistence(phone);
                // 创建用户实体
                UserInfo newUser = buildNewUser(registerInfo);
                // 持久化用户信息
                saveUserWithFallback(newUser);
                // 生成并存储Token
                String token = generateAndStoreToken(newUser);
                // 异步记录注册日志
                publishRegisterEvent(newUser);
                return token;
        }

        /**
         * 构建实体类信息
         * @param registerInfo
         * @return
         */
        @NotNull
        private UserInfo buildNewUser(@NotNull LoginInfo registerInfo) {
                UserInfo user = new UserInfo();
                // 基础信息
                user.setPhone(registerInfo.getPhone());
                //对密码进行加密
                user.setPassword(PasswordUtil.encodePassword(registerInfo.getPassword()));
                // 默认信息
                user.setUsername(generateDefaultUsername(registerInfo.getPhone()));
                user.setAvatar(loadDefaultAvatar());
                user.setGender(0);
                // 系统信息
                user.setStatus(BaseStatus.ENABLE);
                user.setCreateTime(Date.from(Clock.systemDefaultZone().instant()));
                user.setUpdateTime(user.getCreateTime());
                return user;
        }

        /**
         * 判断用户是否存在
         * @param phone
         */
        private void checkUserExistence(String phone) {
                UserInfo userInfo = userInfoMapper.selectByPhone(phone);
                if (userInfo != null) {
                        throw new BaseException(ResultCodeEnum.APP_LOGIN_IS_REGISTERED);
                }
        }

        /**
         * 生成默认用户名
         */
        @NotNull
        private String generateDefaultUsername(@NotNull String phone) {
                return "用户" + phone.substring(phone.length() - 4);
        }

        /**
         * 加载默认头像（带随机选择）
         */
        private String loadDefaultAvatar() {
                List<String> defaultAvatars = Arrays.asList(
                        "https://oss.example.com/avatars/default_01.jpg",
                        "https://oss.example.com/avatars/default_02.jpg",
                        "https://oss.example.com/avatars/default_03.jpg"
                );
                return defaultAvatars.get(ThreadLocalRandom.current().nextInt(defaultAvatars.size()));
        }

        /**
         * 保存用户
         */
        @Transactional(rollbackFor = Exception.class)
        private void saveUserWithFallback(UserInfo user) {
                try {
                        userInfoMapper.saveUserInfo(user);
                } catch (DuplicateKeyException e) {
                        log.warn("用户重复注册，手机号: {}", user.getPhone());
                       throw new BaseException(ResultCodeEnum.APP_LOGIN_IS_REGISTERED);
                }
        }


        /**
         * 异步发布注册事件
         */
        private void publishRegisterEvent(@NotNull UserInfo user) {
                // 异步记录日志
                eventPublisher.publishEvent(new UserRegisterEven(this, user.getPhone()));
        }

        /**
         * 忘记密码
         * @param userInfo
         */
        @Override
        @Transactional
        public void forgetPassword(@NotNull LoginInfo userInfo) {
                // 参数校验已通过@Valid完成
                final String phone = userInfo.getPhone();
                //校验验证码
                validateAndDeleteCode(phone, userInfo.getCode());
                //根据手机号查询用户信息
                UserInfo user = userInfoMapper.selectByPhone(phone);
                //判断用户是否存在
                if(null == user) {
                        throw new BaseException(ResultCodeEnum.APP_LOGIN_IS_NOT_REGISTERED);
                }
                //获取密码
                String password = userInfo.getPassword();
                //对密码进行加密
                password = PasswordUtil.encodePassword(password);
                //更新密码
                user.setPassword(password);
                //设置更新时间
                user.setUpdateTime(new Date());
                //更新用户信息
                userInfoMapper.updateUserInfo(user);
                //记录找回密码日志
                eventPublisher.publishEvent(new UserFindPassowrdEvent(this,userInfo.getPhone()));
        }

        /**
         * 获取用户信息
         * @return
         */
        @Override
        public UserInfoVo getUserInfo() {
               //从当前线程获取手机号
               String phone = LoginUserHolder.getLoginUser().getPhone();
               //根据手机号查询用户信息
               UserInfo userInfo = userInfoMapper.selectByPhone(phone);
               //将用户信息转换为用户信息vo
               UserInfoVo userInfoVo = new UserInfoVo();
               //进行对象拷贝
               BeanUtils.copyProperties(userInfo,userInfoVo);
               //获取用户性别
               String gender = userInfo.getGender().equals(BaseGender.MALE.getCode()) ? "男" : "女";
               userInfoVo.setGender(gender);
               return userInfoVo;
        }

        /**
         * 更新用户信息
         * @param userInfo
         */
        @Override
        public void updateUserInfo(@NotNull UserInfo userInfo) {
                //从当前线程获取手机号
                String phone = LoginUserHolder.getLoginUser().getPhone();
                //设置手机号
                userInfo.setPhone(phone);
                //设置更新时间
                userInfo.setUpdateTime(new Date());
                //更新用户信息
                userInfoMapper.updateUserInfo(userInfo);
        }

        /**
         * 获取验证码
         * @return
         */
        @Override
        public CaptchaVo getCaptcha() {
                //创建一个验证码
                SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);
                String code = specCaptcha.text().toLowerCase();
                String key = RedisConstant.APP_LOGIN_PREFIX + UUID.randomUUID();
                //存入redis
                redisTemplate.opsForValue().set(key,code,RedisConstant.APP_LOGIN_CODE_RESEND_TIME_SEC , TimeUnit.SECONDS);
                return new CaptchaVo(specCaptcha.toBase64(),key);
        }


        /**
         * 修改密码
         * @param oldPassword
         * @param newPassword
         */
        @Override
        public void updatePassword(String oldPassword, String newPassword) {
                //从当前线程获取手机号
                String phone = LoginUserHolder.getLoginUser().getPhone();
                //根据手机号查询用户信息
                UserInfo userInfo = userInfoMapper.selectByPhone(phone);
                //判断密码是否正确
                boolean matches = PasswordUtil.matches(oldPassword,userInfo.getPassword());
                if (!matches) {
                        throw new BaseException(ResultCodeEnum.APP_LOGIN_PASSWORD_NOT_TRUE);
                }
                //对新密码进行加密
                String password = PasswordUtil.encodePassword(newPassword);
                //更新密码
                userInfo.setPassword(password);
                userInfoMapper.updateUserInfo(userInfo);
        }
}
